ios unity 三闪屏设置 unity游戏闪退分析

您所在的位置:网站首页 media encoder闪退 ios unity 三闪屏设置 unity游戏闪退分析

ios unity 三闪屏设置 unity游戏闪退分析

2023-08-23 00:47| 来源: 网络整理| 查看: 265

游戏线上测试总是有一些很奇怪的crash信息上报,闪退点是Unity引擎C++层的方法GameObject::GetSupportedMessagesRecalculate。我们自己平时跑游戏,偶尔也会在场景切换的时候发生闪退。经过初步分析,确定是同一个crash。虽然收集到的闪退率不高,但既然我们自己人都碰到了,那线上实际情况可能会更容易出。

结论很简单,想看结论,直接跳到末尾即可。分析过程很坎坷,断断续续跨了有两三个月。分析过程分为两个阶段,阶段一主要是围绕崩溃点本身进行的分析,没有得出结论;阶段二,是在编辑器中复现出来的另外一种情况,最终找到了突破点。

阶段一简略crash堆栈

从名字上猜测,是资源加载出来的时候出了问题,很可能是资源损坏了。

GameObject::GetSupportedMessagesRecalculate() GameObject::SetSupportedMessagesDirty() MonoBehaviour::AwakeFromLoad(AwakeFromLoadMode) AwakeFromLoadQueue::PersistentManagerAwakeSingleObject(Object&, AwakeFromLoadMode) TimeSliceAwakeFromLoadQueue::IntegrateTimeSliced(int) PreloadManager::UpdatePreloadingSingleStep(PreloadManager::UpdatePreloadingFlags, int) PreloadManager::UpdatePreloading()详细crash信息

所幸在开发环境下,复现了一次,拿到了比较详细的堆栈信息。

E/CRASH: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** Version '2019.4.16f1 (e05b6e02d63e)', Build type 'Development', Scripting Backend 'mono', CPU 'armeabi-v7a' Build fingerprint: 'OPPO/R9s/R9s:6.0.1/MMB29M/1528528402:user/release-keys' Revision: '0' ABI: 'arm' Timestamp: 2021-08-13 12:39:01+0800 pid: 18030, tid: 18096, name: UnityMain >>> com.stormx.test GetComponentPtr()->SupportedMessagesDidChange(m_SupportedMessages); } } void GameObject::GetSupportedMessagesRecalculate() { Assert(!IsDestroying()); m_SupportedMessages = 0; for (Container::iterator i = m_Component.begin(); i != m_Component.end(); ++i) if (i->GetComponentPtr()) // !crash! m_SupportedMessages |= i->GetComponentPtr()->CalculateSupportedMessages(); }反汇编

用IDA反编译一下libunity.so。 这个库位于Unity安装目录的Editor\Data\PlaybackEngines\AndroidPlayer\Variations目录中,如果android打包是mono debug模式, 为mono\Development\Libs\armeabi-v7a\libunity.so;如果是il2cpp debug模式,为il2cpp\Development\Libs\armeabi-v7a\libunity.so;如果是release版本,把路径中的Development换成Release;如果是64位模式,把路径中的armeabi-v7a换成arm64-v8a。

对汇编不熟悉,只能边查资料,结合源码来分析。从crash的位置能够定位到发生闪退的指令位置为: #00 pc 0040f05e, 为了方便解读,以下反编译代码顺序略有调整:

.text:0040F04C ; _DWORD GameObject::GetSupportedMessagesRecalculate(GameObject *__hidden this) .text:0040F04C _ZN10GameObject31GetSupportedMessagesRecalculateEv .text:0040F04C ; CODE XREF: GameObject::SetSupportedMessagesDirty(void)+16↑p .text:0040F04C ; __unwind { .text:0040F04C PUSH {R4,R5,R7,LR} .text:0040F04E LDR R2, [R0,#0x3C] // r2 = m_Component.size(). r2 == 3, 有三个组件 .text:0040F052 LDR R1, [R0,#0x2C] // r1 = m_Component.begin() .text:0040F050 MOV R4, R0 // r4 = r0 = this .text:0040F054 MOVS R0, #0 .text:0040F058 STR R0, [R4,#0x50] // m_SupportedMessages = 0; .text:0040F056 CMP R2, #0 // 判断m_Component.size() 是否等于 0 .text:0040F05A BEQ locret_40F07C // if == 0 goto locret_40F07C .text:0040F05C MOV R5, R1 // Container::iterator i = m_Component.begin() .text:0040F05E .text:0040F05E loc_40F05E ; CODE XREF: GameObject::GetSupportedMessagesRecalculate(void)+2E↓j .text:0040F05E !crash! LDR R0, [R5,#4] // component = i->GetComponentPtr() .text:0040F060 CBZ R0, loc_40F072 //if (i == nullptr) goto loc_40F072 .text:0040F062 LDR R1, [R0] .text:0040F064 LDR R1, [R1,#0x58] // r1 = i->GetComponentPtr()->CalculateSupportedMessages .text:0040F066 BLX R1 // call CalculateSupportedMessages() .text:0040F068 LDR R1, [R4,#0x2C] // r1 = this->m_Component.begin() .text:0040F06A LDR R2, [R4,#0x3C] // r2 = this->m_Component.size() .text:0040F06C LDR R3, [R4,#0x50] // r3 = this->m_SupportedMessages .text:0040F06E ORRS R0, R3 // ret |= this->m_SupportedMessages .text:0040F070 STR R0, [R4,#0x50] // this->m_SupportedMessages = ret .text:0040F072 .text:0040F072 loc_40F072 ; CODE XREF: GameObject::GetSupportedMessagesRecalculate(void)+14↑j .text:0040F072 ADD.W R0, R1, R2,LSL#3 // r0 = r1 + r2 GetComponentPtr())的时候发生的,根据寄存器r5的值来看,此时i为NULL。有下面两种情况,会导致i为NULL:

假设m_Component.begin()为空,则迭代器i会是空。此时m_Component.size()也应该是0,则for循环压根就不会进入。说明假设不成立;假设m_Component.begin()不为空,则迭代器i不会是空,i只有++操作,不可能变成空。

也就是说,i无论如何都不可能是空值。那就说名有可能出现了内存错误:

当前的GameObject已经被销毁了!此时this指针就是非法地址,理论上说, 执行this->m_SupportedMessages = 0这一步时就会出现崩溃。当然,崩溃信息也不一定完全准确,而且两行条指令相邻,极有可能发生。多线程问题。指令0040F04E和0040F052之间被多线程操作打断,别的地方销毁了m_Components。 中间就隔了一条之类,这种情况理论上概率极低。

分析到此为止,陷入了僵局,无法继续推进。只能猜测是某个资源损坏了,但是一直没发定位到是哪个资源。在网上搜索了下,也没有太多案例可以参考。

阶段二

很长一段时间后,就想着用编辑来模拟一下bundle的运行情况,看看能不能获得更详细的报错信息。经过若干次测试,终于在某个特定的情况下切换场景,碰到了大量的错误日志。并且编辑器停止游戏运行的时候,编辑器发生了闪退。

编辑器闪退堆栈:

========== OUTPUTTING STACK TRACE ================== 0x00007FF7A53FE8A4 (Unity) GameObject::GetComponentIndex 0x00007FF7A5C8804E (Unity) CanReplaceComponent 0x00007FF7A5C87B50 (Unity) CanDestroyObject 0x00007FF7A5C8ADDF (Unity) DestroyObjectHighLevel 0x00007FF7A5CA08D3 (Unity) DestroyWorldObjects 0x00007FF7A45992ED (Unity) EditorSceneManager::RestoreSceneBackups 0x00007FF7A3FEE82E (Unity) PlayerLoopController::ExitPlayMode 0x00007FF7A4000CCF (Unity) PlayerLoopController::SetIsPlaying 0x00007FF7A40039A2 (Unity) Application::TickTimer 0x00007FF7A49874E5 (Unity) MainMessageLoop 0x00007FF7A49916C8 (Unity) WinMain 0x00007FF7A7A06962 (Unity) __scrt_common_main_seh 0x00007FFB875F7034 (KERNEL32) BaseThreadInitThunk 0x00007FFB88642651 (ntdll) RtlUserThreadStart ========== END OF STACKTRACE ===========

编辑器的闪退堆栈没有太大价值,因为是在停止播放时发生的,而不是在出错位置。但是从堆栈上可以猜测出是某个GameObject或Component发生了野指针,导致销毁的时候引起了闪退。

编辑器使用bundle模式运行,收集到的错误日志:

Component at index 0 could not be loaded when loading game object 'Bip001'. Removing it! (Filename: C:\buildslave\unity\build\Runtime/BaseClasses/GameObject.cpp Line: 811) Transform component could not be found on game object. Adding one! (Filename: C:\buildslave\unity\build\Runtime/BaseClasses/GameObject.cpp Line: 741) Prefab has multiple Transform components! Removing them automatically would not be safe. (Filename: C:\buildslave\unity\build\Runtime/BaseClasses/GameObject.cpp Line: 890) CheckConsistency: GameObject does not reference component Transform. Fixing. (Filename: C:\buildslave\unity\build\Runtime/BaseClasses/GameObject.cpp Line: 1394)

而错误日志也是让人很困惑,没有指明是哪个资源出了问题。即便我把含有’Bip001’的所有结点全部删掉,又会出现另外一些结点出错。在网上查了一下,有相似的问题,都是资源损坏引起的:

prefab在版本合并时,出现了合并混乱,导致prefab格式被破坏;资源是旧版Unity生成的,升级Unity后资源格式需要升级,或者bundle需要重新生成;prefab中含有丢失的内嵌预设(Missing Prefab);资源中含有丢失的脚本(Missing Script);CacheServer中资源发生了损坏;Library缓存目录中的资源发生了损坏。

用脚本扫描了所有的资源,确实出现很多损坏问题。把资源问题逐一修复后,删除了所有缓存,重新打bundle,结果还是一样,失望ing。

不过,至此可以排除是资源损坏的问题。回到出问题的地方,刚好是切换场景,那最有可能的就是某个资源正在异步加载或对象在创建的过程中,被切换场景给销毁了。Unity创建对象的接口只有Instantiate,而且实例化对象是同步的。那就只可能资源在异步加载的过程中,bundle被Unload引起了异常。查了下资源加载器代码,果然在异步加载资源的时候,没有对bundle增加引用计数,导致切换场景的时候被释放掉了。至于Unity为何没有拦截掉这种错误的用法,就不得而知了。

清除Missing ScriptGameObjectUtility.RemoveMonoBehavioursWithMissingScript(GameObject go);查找内嵌的Missing Prefabstatic void FindMissingPrefab(GameObject go, string name, bool isRoot, bool recursive = true) { if (go.name.Contains("Missing Prefab")) { Debug.LogError($"1. {name} has missing prefab {go.name}", go); return; } if (PrefabUtility.IsPrefabAssetMissing(go)) { Debug.LogError($"2. {name} has missing prefab {go.name}", go); return; } if (PrefabUtility.IsDisconnectedFromPrefabAsset(go)) { Debug.LogError($"3. {name} has missing prefab {go.name}", go); return; } if (!isRoot) { if (PrefabUtility.IsAnyPrefabInstanceRoot(go)) { return; } GameObject prefabRoot = PrefabUtility.GetNearestPrefabInstanceRoot(go); if (prefabRoot == go) { return; } } if (recursive) { name = name + "/" + go.name; foreach (Transform child in go.transform) { FindMissingPrefab(child.gameObject, name, false, recursive); } } }总结

卸载正在异步加载资源的AssetBundle,会导致Unity引擎内部出现指针错误,引发一些奇怪的闪退问题。 经过此次闪退分析,基本上可以确定,堆栈含有MonoBehaviour::AwakeFromLoad(AwakeFromLoadMode),都是资源损坏引起的。可能是资源真的有问题,或AssetBundle损坏了,或资源正在加载过程中AssetBundle被释放了。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3